Skip to content

fix(library): enforce spec-compliant $ref serialization; add Extensions support for schema references in v3.1/v3.2#2782

Merged
baywet merged 9 commits intomainfrom
copilot/fix-openapi-schema-reference-description
Mar 20, 2026
Merged

fix(library): enforce spec-compliant $ref serialization; add Extensions support for schema references in v3.1/v3.2#2782
baywet merged 9 commits intomainfrom
copilot/fix-openapi-schema-reference-description

Conversation

Copy link
Contributor

Copilot AI commented Mar 17, 2026

Per OpenAPI spec, sibling keywords alongside $ref are version-dependent. For v2/v3.0 all siblings must be dropped; for v3.1/v3.2 schema refs, peer keywords including extensions are allowed; for v3.1/v3.2 non-schema refs, only summary and description are permitted. This PR addresses the gap in extension support for schema references.

Description

OpenApiSchemaReference correctly drops sibling keywords (description, title, etc.) for v2/v3.0 serialization, but lacked support for serializing/deserializing extension properties (x-*) alongside $ref in v3.1/v3.2, and was missing a SerializeAsV32 override with loop detection.

Type of Change

  • New feature (non-breaking change which adds functionality)

Related Issue(s)

Changes Made

JsonSchemaReference

  • Added Extensions property (IDictionary<string, IOpenApiExtension>?) — serialized only in v3.1/v3.2, silently dropped for v2/v3.0
  • Updated copy constructor to copy extensions
  • SerializeAdditionalV3XProperties now calls writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1)
  • SetAdditional31MetadataFromMapNode now parses x-* properties from $ref sibling keywords into Extensions

OpenApiSchemaReference

  • Extensions upgraded from getter-only (Target?.Extensions) to get/set — reference-level extensions take priority over target
  • Added SerializeAsV32 override with loop detection (matches existing SerializeAsV31 pattern)

PublicAPI.Unshipped.txt

  • Registered: JsonSchemaReference.Extensions (get/set), OpenApiSchemaReference.Extensions.set, OpenApiSchemaReference.SerializeAsV32

Tests

  • Updated SerializeSchemaReferenceAsV31JsonWorks to include an extension; updated verified snapshots
  • Added SerializeSchemaReferenceAsV32JsonWorks + snapshots
  • Added ParseSchemaReferenceWithExtensionsWorks — parses x-* from a v3.1 $ref node
  • Added SchemaReferenceExtensionsNotWrittenInV30 / SchemaReferenceExtensionsNotWrittenInV2 — asserts only $ref is emitted
// v3.1: extensions written alongside $ref
var schemaRef = new OpenApiSchemaReference("Pet", doc)
{
    Description = "Overridden description",
    Extensions = new Dictionary<string, IOpenApiExtension>
    {
        ["x-custom"] = new JsonNodeExtension(JsonValue.Create("value"))
    }
};
// Serializes as: { "description": "...", "x-custom": "value", "$ref": "#/components/schemas/Pet" }

// v3.0: ONLY $ref emitted — description and extensions silently dropped
// { "$ref": "#/components/schemas/Pet" }

Testing

  • Unit tests added/updated
  • All existing tests pass

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Versions applicability

  • My change applies to the version 1.X of the library, if so PR link:
  • My change applies to the version 2.X of the library, if so PR link:
  • My change applies to the version 3.X of the library, if so PR link:
  • I have evaluated the applicability of my change against the other versions above.

Additional Notes

Extension support is intentionally scoped to JsonSchemaReference (schema refs only). Non-schema reference objects (OpenApiReferenceWithDescription, OpenApiReferenceWithDescriptionAndSummary) remain extension-free per the v3.1 Reference Object spec, which explicitly prohibits additional properties.

Original prompt

This section details on the original issue you should resolve

<issue_title>OpenApiSchemaReference allows Description alongside $ref producing non-spec-compliant output in OAS 3.0 (clone of #2745)</issue_title>
<issue_description>## Description

The Microsoft.OpenApi library allows setting Description on OpenApiSchemaReference, which results in serialized output containing both $ref and description.

However, according to OpenAPI 3.0.x specification:

When a $ref is used, all other properties SHALL be ignored.

This makes $ref effectively a replacement, meaning sibling keywords like description should not appear.


Example

Code

using System;
using System.IO;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;

class Program
{
    static void Main()
    {
        var document = new OpenApiDocument
        {
            Info = new OpenApiInfo
            {
                Title = "Ref Description Test",
                Version = "1.0.0"
            },

            Components = new OpenApiComponents
            {
                Schemas =
                {
                    ["Pet"] = new OpenApiSchema
                    {
                        Type = "object",
                        Properties =
                        {
                            ["name"] = new OpenApiSchema { Type = "string" }
                        }
                    }
                }
            },

            Paths = new OpenApiPaths
            {
                ["/test"] = new OpenApiPathItem
                {
                    Operations =
                    {
                        [OperationType.Get] = new OpenApiOperation
                        {
                            Responses =
                            {
                                ["200"] = new OpenApiResponse
                                {
                                    Description = "OK",
                                    Content =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                        {
                                            Schema = new OpenApiSchemaReference("Pet")
                                            {
                                                Description = "Local description"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        };

        using var stream = new MemoryStream();
        using var writer = new OpenApiJsonWriter(new StreamWriter(stream));

        document.SerializeAsV3(writer);
        writer.Flush();

        stream.Position = 0;
        Console.WriteLine(new StreamReader(stream).ReadToEnd());
    }
}

Output

$ref: '#/components/schemas/Pet'
description: Local description

Expected behavior

One of:

  1. Serializer wraps in allOf
allOf:
  - $ref: '#/components/schemas/Pet'
description: Local description
  1. Description ignored when targeting OAS 3.0

  2. Validation warning


Notes


Question

What is the intended behavior when serializing references with annotations for OAS 3.0 vs 3.1?
</issue_description>

<agent_instructions>make sure you thoroughly consider my reply to implement the changes and tests</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@baywet Hi @mdaneri,

Thank you for using the SDK and for reaching out.

I think we need to break this down further:

OpenAPI 2 & 3.0

For any kind of reference (schema, path item, etc..), no sibling keyworks (description, summary, anything really) are allowed. We should drop them during serialization. Extension are not allowed either.

Source specification for v2

Source specification for v3

OpenAPI 3.1 & 3.2

For JSON Schema references, any keyword that's defined as a "peer keyword" can appear alongside the $ref property. That's effectively anything that has a setter in the reference type. Extensions are also allowed.

Source for schema object

For any other kind of reference (path item, etc...), ONLY summary and description are allowed alongside ...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Fix OpenApiSchemaReference non-spec-compliance in output fix(library): enforce spec-compliant $ref serialization; add Extensions support for schema references in v3.1/v3.2 Mar 17, 2026
Copilot AI requested a review from baywet March 17, 2026 18:16
@baywet baywet marked this pull request as ready for review March 17, 2026 18:45
@baywet baywet requested a review from a team as a code owner March 17, 2026 18:45
@baywet baywet enabled auto-merge March 17, 2026 18:45
Copilot AI and others added 7 commits March 20, 2026 09:04
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
…3.2; add SerializeAsV32 with loop detection

- Add Extensions property to JsonSchemaReference (serialize in v3.1/v3.2 only)
- Add Extensions setter to OpenApiSchemaReference (checks reference-level extensions first)
- Add SerializeAsV32 override to OpenApiSchemaReference with loop detection
- Read extension properties from $ref sibling keywords during v3.1 deserialization
- Add tests for extensions in v3.1/v3.2 and verify extensions dropped in v3.0/v2

Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
…ema references

Signed-off-by: Vincent Biret <vibiret@microsoft.com>
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
@baywet baywet force-pushed the copilot/fix-openapi-schema-reference-description branch from befb8f3 to cba3dda Compare March 20, 2026 13:07
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
@sonarqubecloud
Copy link

@baywet baywet merged commit 9bf61de into main Mar 20, 2026
18 checks passed
@baywet baywet deleted the copilot/fix-openapi-schema-reference-description branch March 20, 2026 15:52
baywet added a commit that referenced this pull request Mar 20, 2026
…ference-description

fix(library): enforce spec-compliant $ref serialization; add Extensions support for schema references in v3.1/v3.2
baywet added a commit that referenced this pull request Mar 20, 2026
Merge pull request #2782 from microsoft/copilot/fix-openapi-schema-reference-description
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenApiSchemaReference allows Description alongside $ref producing non-spec-compliant output in OAS 3.0 (clone of #2745)

3 participants